package de.ubschmidt.capi;

import java.nio.ByteBuffer;

/**
 * Utility class for working with CAPI structs.
 *
 * <p>A CAPI struct is coded as an array of octets, the first octet
 * containing the length of the subsequent array. If the first octet has
 * the value 255 (0xFF), this is an escape code: the following word is
 * then interpreted as containing the length of the following data. An
 * empty struct is coded as a single octet with value 0.
 */
public class CapiStruct {

    private static final byte ESCAPE_CODE = (byte) 0xff;

    private byte [] _buf;

    public CapiStruct(final ByteBuffer byteBuffer) {
        _buf = readStructContent(byteBuffer);
    }

    public String toString() {
        String s = String.format("%s[len=%d buf[",
                getClass().getSimpleName(),
                _buf.length);

        for (int i = 0; i < _buf.length; ++i) {
            if (i > 0) {
                s += " ";
            }

            s += String.format("0x%02x", _buf[i]);
        }

        s += "]]";

        return s;
    }

    /**
     * Retrieve the content of a CAPI struct from the
     * specified {@code ByteBuffer}.
     *
     * <p>
     *
     * @param byteBuffer
     * @return
     */
    public static byte [] readStructContent(final ByteBuffer byteBuffer) {
        int length = readStructLength(byteBuffer);

        byte [] buf = new byte [length];
        byteBuffer.get(buf, 0, length);

        return buf;
    }

    /**
     *
     * @param byteBuffer
     */
    public static void skipStruct(final ByteBuffer byteBuffer) {
        int length = readStructLength(byteBuffer);

        byteBuffer.position(byteBuffer.position() + length);
    }

    /**
     * Retrieves the length of struct content starting at the current position
     * of the specified {@code ByteBuffer}.
     *
     * @param  byteBuffer
     *
     * @return  The length of the struct content.
     */
    public static int readStructLength(final ByteBuffer byteBuffer) {
        byte lengthByte = byteBuffer.get();

        if (lengthByte == ESCAPE_CODE) {
            short lengthShort = byteBuffer.getShort();
            return unsigned(lengthShort);
        } else {
            return unsigned(lengthByte);
        }
    }

    /**
     * Write the content length of a CAPI struct to current position of the
     * specified {@code ByteBuffer}.
     *
     * <p>A length below 255 byte is encoded as a single byte. If the length is
     * 255 or greater then the length is encoded as a sequence of three bytes.
     * The first byte is an escape code (0xff) and the following word is the
     * length. The maximum length is 65535 byte.
     *
     * @param byteBuffer The {@code ByteBuffer}.
     * @param length
     * @throws IllegalArgumentException An {@code IllegalArgumentException} is
     *         thrown, if the specified length exceeds the maximum allowable
     *         length of 65535.
     */
    public static void writeStructLength(final ByteBuffer byteBuffer,
            final int length) {
        if (length > 65535) {
            throw new IllegalArgumentException("length " + length
                    + " exceeds maximum length of CAPI struct contents");
        }

        if (length < 255) {
            byteBuffer.put((byte) length);
        } else {
            byteBuffer.put(ESCAPE_CODE);
            byteBuffer.putShort((short) length);
            writeStructLength(byteBuffer, length);
        }
    }

    /**
     * Convert the signed {@code byte} value to an unsigned integer. dfsdf
     *
     * XXX move to util class
     *
     * @param value
     * @return
     */
    private static int unsigned(final byte value) {
        return value & 0xff;
    }

    /**
     * Convert the signed {@code short} value to an unsigned integer.
     *
     * XXX move to util class
     *
     * @param value
     * @return
     */
    private static int unsigned(final short value) {
        return value & 0xffff;
    }

}
